/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */
package com.pig4cloud.pigx.strategy.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.ApiRequestException;
import com.pig4cloud.pigx.common.customize.exception.DuckweedException;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.redis.constants.CommonRedisConstants;
import com.pig4cloud.pigx.common.customize.redis.constants.RedisKeyEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.strategy.bo.StrategyTaskContractCacheBO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractAddDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractCloseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractConditionDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractEditDTO;
import com.pig4cloud.pigx.strategy.entity.StrategyContract;
import com.pig4cloud.pigx.strategy.entity.StrategyGroupContract;
import com.pig4cloud.pigx.strategy.entity.StrategySwitchContract;
import com.pig4cloud.pigx.strategy.entity.StrategyTaskContract;
import com.pig4cloud.pigx.strategy.enums.StrategyContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategySwitchContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyTaskContractEnum;
import com.pig4cloud.pigx.strategy.executor.contract.order.StrategyOrderContractHandlerManager;
import com.pig4cloud.pigx.strategy.executor.contract.order.supporter.StrategyOrderContractCalculator;
import com.pig4cloud.pigx.strategy.executor.contract.task.StrategyTaskContractHandler;
import com.pig4cloud.pigx.strategy.executor.contract.task.supporter.StrategyTaskContractCalculator;
import com.pig4cloud.pigx.strategy.mapper.StrategyContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyGroupContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategySwitchContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyTaskContractMapper;
import com.pig4cloud.pigx.strategy.mapstruct.StrategyContractStruct;
import com.pig4cloud.pigx.strategy.service.StrategyContractService;
import com.pig4cloud.pigx.strategy.util.StrategyContractUtil;
import com.pig4cloud.pigx.strategy.vo.StrategyContractAppDetailVO;
import com.pig4cloud.pigx.strategy.vo.StrategyContractAppVO;
import com.pig4cloud.pigx.strategy.vo.StrategyContractProfitVO;
import com.pig4cloud.pigx.strategy.vo.StrategyContractVO;
import com.pig4cloud.pigx.system.constant.SystemPlatformConstant;
import com.pig4cloud.pigx.system.entity.SystemPlatform;
import com.pig4cloud.pigx.system.entity.SystemProduct;
import com.pig4cloud.pigx.system.entity.SystemSymbol;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 策略-合约
 *
 * @since 2022-11-24 10:39:27
 */
@Service
@RequiredArgsConstructor
public class StrategyContractServiceImpl extends ServiceImpl<StrategyContractMapper, StrategyContract> implements StrategyContractService {

    private final StrategySwitchContractMapper strategySwitchContractMapper;
    private final StrategyTaskContractMapper strategyTaskContractMapper;
    private final StrategyGroupContractMapper strategyGroupContractMapper;
    private final StrategyTaskContractHandler strategyTaskContractHandler;
    private final StrategyOrderContractHandlerManager strategyOrderContractHandlerManager;

    private final SecurityUtil securityUtil;
    private final RedisUtils redisUtils;
    private final StrategyContractUtil strategyContractUtil;
    private final MysqlUtil mysqlUtil;

    /**
     * 根据产品id统计相关策略的条数
     *
     * @param productId 产品id
     * @return {@link Long}
     * @since 2022/11/26 14:48
     */
    @Override
    public Long countByProductId(Long productId) {

        QueryWrapper<StrategyContract> query = new QueryWrapper<>();
        query.lambda()
                .eq(StrategyContract::getProductId, productId);

        return baseMapper.selectCount(query);

    }

    /**
     * 分页查询
     *
     * @param page      分页条件
     * @param condition 查询条件
     * @return {@link Page< StrategyContractVO>}
     * @since 2022/11/24 10:48
     */
    @Override
    public Page<StrategyContractVO> page(Page<StrategyContract> page, StrategyContract condition) {
        QueryWrapper<StrategyContract> query = new QueryWrapper<>();
        if (condition.getPlatformId() != null) {
            query.eq("platform_id", condition.getPlatformId());
        }
        query.lambda()
                .orderByDesc(StrategyContract::getCreateTime);
        page = baseMapper.selectPage(page, query);
        List<StrategyContract> list = page.getRecords();
        if (CollectionUtils.isEmpty(list)) {
            return MysqlUtil.EMPTY_PAGE;
        }

        List<StrategyContractVO> voList = StrategyContractStruct.INSTANCE.list2VOList(list);

        // 归集平台id
        Set<Long> platformIdSet = voList.stream()
                .map(StrategyContractVO::getPlatformId)
                .collect(Collectors.toSet());
        // 查询平台信息
        List<SystemPlatform> platformList = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, platformIdSet);

        // 归集产品id
        Set<Long> productIdSet = voList.stream()
                .map(StrategyContractVO::getProductId)
                .collect(Collectors.toSet());
        // 查询产品信息
        List<SystemProduct> productList = redisUtils.getPersistentCache(RedisKeyEnum.PRODUCT, productIdSet);

        // 归集策略组id
        List<Long> groupIdList = voList.stream()
                .map(StrategyContractVO::getGroupId)
                .collect(Collectors.toList());
        // 查询策略组信息
        List<StrategyGroupContract> groupList = strategyGroupContractMapper.selectBatchIds(groupIdList);

        // 遍历
        voList.forEach(i -> {
            // 设置字典项
            i.setStatus(CommonDictEnum.Status.findName(i.getStatus()));
            // 策略组名称
            StrategyGroupContract groupContract = new StrategyGroupContract().selectById(i.getGroupId());
            i.setGroupName(groupContract.getName());

            // 设置平台名称
            for (SystemPlatform platform : platformList) {
                if (Objects.equals(platform.getId(), i.getPlatformId())) {
                    i.setPlatformName(platform.getName());
                    break;
                }
            }

            // 设置产品名称
            for (SystemProduct product : productList) {
                if (Objects.equals(product.getId(), i.getProductId())) {
                    i.setProductName(product.getName());
                    break;
                }
            }

            // 设置策略组名称
            for (StrategyGroupContract group : groupList) {
                if (Objects.equals(group.getId(), i.getGroupId())) {
                    i.setGroupName(group.getName());
                    break;
                }
            }
        });

        return MysqlUtil.transferPage(page, voList);
    }

    /**
     * 新增
     *
     * @param dto 数据
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void add(StrategyContractAddDTO dto) {

        StrategyContract entity = StrategyContractStruct.INSTANCE.addDTO2entity(dto);
        validParam(entity);

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        entity.setCreateName(user.getName());
        entity.setUpdateName(user.getName());
        securityUtil.initCreateAbout(entity, user, now);

        if (baseMapper.insert(entity) != 1) {
            throw new DuckweedException("操作失败");
        }

        // 刷新缓存
        redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, entity.getId(), entity);

    }

    /**
     * 校验参数
     *
     * @param entity 实体类
     * @since 2022/11/26 17:50
     */
    private void validParam(StrategyContract entity) {

        // 首仓方向是双向时，下单数量必须大于等于2
        if (Objects.equals(StrategyContractEnum.Side.DOUBLE_SIDE.getValue(), entity.getSide())) {
            if (entity.getOrderNum() < 2) {
                throw new DuckweedException("首仓方向是双向时，下单数量必须大于等于2");
            }

        } else if (entity.getOrderNum() < 1) {
            throw new DuckweedException("首仓方向是单向时，下单数量必须大于等于1");
        }

    }

    /**
     * 修改
     *
     * @param dto 数据
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void edit(StrategyContractEditDTO dto) {

        StrategyContract entity = StrategyContractStruct.INSTANCE.editDTO2entity(dto);
        validParam(entity);

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        entity.setUpdateName(user.getName());
        securityUtil.initUpdateAbout(entity, user, now);

        if (baseMapper.edit(entity) != 1) {
            throw new DuckweedException("操作失败");
        }

        // 刷新缓存
        entity = baseMapper.selectById(entity.getId());
        redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, entity.getId(), entity);

    }

    /**
     * 根据id逻辑删除
     *
     * @param id id
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void del(Long id) {

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        // 检查引用
        checkReferences(id);

        UpdateWrapper<StrategyContract> update = new UpdateWrapper<>();
        update.lambda()
                .set(StrategyContract::getUpdateBy, user.getId())
                .set(StrategyContract::getUpdateName, user.getName())
                .set(StrategyContract::getUpdateTime, now)
                .set(StrategyContract::getIsDeleted, null)
                .eq(StrategyContract::getId, id);
        if (baseMapper.update(null, update) != 1) {
            throw new DuckweedException("操作失败");
        }

        // 刷新缓存
        redisUtils.delPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, id);

    }

    /**
     * 检查引用
     *
     * @param id id
     * @since 2022/11/24 16:04
     */
    private void checkReferences(Long id) {

        // 检查在策略开关-合约表strategy_switch_contract中的引用
        QueryWrapper<StrategySwitchContract> querySwitch = new QueryWrapper<>();
        querySwitch.lambda()
                .eq(StrategySwitchContract::getStrategyId, id)
                .in(StrategySwitchContract::getStatus,
                        StrategySwitchContractEnum.Status.PAUSE.getValue(),
                        StrategySwitchContractEnum.Status.RUNNING.getValue(),
                        StrategySwitchContractEnum.Status.ERROR.getValue());
        Long switchCount = strategySwitchContractMapper.selectCount(querySwitch);
        if (switchCount > 0) {
            throw new DuckweedException("该策略下存在运行中/异常的任务");
        }

    }

    /**
     * 禁用策略组
     *
     * @param id id
     * @since 2022/11/26 17:57
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void disable(Long id) {

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        UpdateWrapper<StrategyContract> update = new UpdateWrapper<>();
        update.lambda()
                .set(StrategyContract::getUpdateBy, user.getId())
                .set(StrategyContract::getUpdateTime, now)
                .set(StrategyContract::getUpdateName, user.getName())
                .set(StrategyContract::getStatus, CommonDictEnum.Status.DEPRECATED.getValue())
                .eq(StrategyContract::getId, id)
                .eq(StrategyContract::getStatus, CommonDictEnum.Status.NORMAL.getValue())
                .eq(StrategyContract::getIsDeleted, CommonDictEnum.IsDeleted.NO.getValue());
        if (baseMapper.update(null, update) != 1) {
            throw new DuckweedException("数据状态已发生改变");
        }

        // 刷新缓存
        StrategyContract entity = baseMapper.selectById(id);
        redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, entity.getId(), entity);

    }

    /**
     * 启用策略组
     *
     * @param id id
     * @since 2022/11/26 17:57
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void enable(Long id) {

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        UpdateWrapper<StrategyContract> update = new UpdateWrapper<>();
        update.lambda()
                .set(StrategyContract::getUpdateBy, user.getId())
                .set(StrategyContract::getUpdateTime, now)
                .set(StrategyContract::getUpdateName, user.getName())
                .set(StrategyContract::getStatus, CommonDictEnum.Status.NORMAL.getValue())
                .eq(StrategyContract::getId, id)
                .eq(StrategyContract::getStatus, CommonDictEnum.Status.DEPRECATED.getValue())
                .eq(StrategyContract::getIsDeleted, CommonDictEnum.IsDeleted.NO.getValue());
        if (baseMapper.update(null, update) != 1) {
            throw new DuckweedException("数据状态已发生改变");
        }

        // 刷新缓存
        StrategyContract entity = baseMapper.selectById(id);
        redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, entity.getId(), entity);

    }

    /**
     * App端开启策略
     *
     * @param id id
     * @since 2022/12/3 19:43
     */
    @Override
    public void appOpen(Long id) {
        strategyTaskContractHandler.open(id);
    }

    /**
     * App端关闭策略
     *
     * @param id id
     * @since 2022/12/3 19:43
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void appClose(Long id) {

        PigxUser user = SecurityUtil.getLoginUserNotNull();

        // 根据用户id、策略id查询正在运行的开关
        QueryWrapper<StrategySwitchContract> querySwitch = new QueryWrapper<>();
        querySwitch.lambda()
                .select(StrategySwitchContract::getId)
                .eq(StrategySwitchContract::getStrategyId, id)
                .eq(StrategySwitchContract::getUserId, user.getId())
                .in(StrategySwitchContract::getStatus,
                        StrategySwitchContractEnum.Status.PAUSE.getValue(),
                        StrategySwitchContractEnum.Status.RUNNING.getValue(),
                        StrategySwitchContractEnum.Status.ERROR.getValue());
        StrategySwitchContract switchContract = strategySwitchContractMapper.selectOne(querySwitch);
        if (Objects.isNull(switchContract)) {
            throw new DuckweedException("状态已发生改变");
        }

        StrategyContractCloseDTO dto = new StrategyContractCloseDTO();
        dto.setSwitchId(switchContract.getId());
        dto.setRemark("用户关闭");
        dto.setSystem(false);
        dto.setNeedRedisLock(true);
        strategyTaskContractHandler.close(dto);

    }

    /**
     * 初始化redis缓存
     */
    @Override
    public void initRedis() {
        try {
            mysqlUtil.startLocalTx();
            List<StrategyContract> list = new StrategyContract().selectList(new QueryWrapper<StrategyContract>() {{
                last("for update");
            }});
            for (StrategyContract contract : list) {
                redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_CONTRACT, contract.getId(), contract);
            }
            mysqlUtil.commitLocalTx();
        } catch (Exception e) {
            mysqlUtil.rollbackLocalTx();
        }
    }

    /**
     * app端查询列表
     *
     * @param condition 查询条件
     * @return {@link List<StrategyContractAppVO>}
     * @since 2022/12/8 16:34
     */
    @Override
    public List<StrategyContractAppVO> appList(StrategyContractConditionDTO condition) {

        // 查询策略、利润、任务执行状态
        List<StrategyContractAppVO> list = baseMapper.appList(condition);
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }

        // 归集币对名称
        Set<String> symbolNameSet = list.stream()
                .map(StrategyContractAppVO::getSymbolName)
                .collect(Collectors.toSet());
        // 查询币对
        List<SystemSymbol> symbolList = redisUtils.getPersistentCache(RedisKeyEnum.SYMBOL, symbolNameSet);

        list.forEach(i -> {
            // 翻译字典项
            i.setSide(StrategyContractEnum.Side.findName(i.getSide()));
            // 设置币对图标
            for (SystemSymbol symbol : symbolList) {
                if (Objects.equals(i.getSymbolName(), symbol.getName())) {
                    i.setImage(symbol.getImage());
                    break;
                }
            }
        });

        // 查询用户运行中的开关
        QueryWrapper<StrategySwitchContract> querySwitch = new QueryWrapper<>();
        querySwitch.lambda()
                .eq(StrategySwitchContract::getUserId, condition.getUserId())
                .in(StrategySwitchContract::getStatus,
                        StrategySwitchContractEnum.Status.PAUSE.getValue(),
                        StrategySwitchContractEnum.Status.RUNNING.getValue(),
                        StrategySwitchContractEnum.Status.ERROR.getValue());
        StrategySwitchContract switchContract = strategySwitchContractMapper.selectOne(querySwitch);
        if (Objects.isNull(switchContract)) {
            return list;
        }

        // 寻找到正在运行的策略
        Optional<StrategyContractAppVO> strategyOpt = list.stream()
                .filter(i -> Objects.equals(i.getId(), switchContract.getStrategyId()))
                .findFirst();
        if (!strategyOpt.isPresent()) {
            return list;
        }
        StrategyContractAppVO strategy = strategyOpt.get();

        // 获取产品信息
        SystemProduct product = strategyContractUtil.getSystemProductCache(strategy.getProductId(), false);

        // 向策略上设置开关信息
        strategy.setSwitchStatus(StrategySwitchContractEnum.Status.findName(switchContract.getStatus()));
        try {

            // 查询仓位信息
            JSONArray positions = strategyOrderContractHandlerManager.positions(condition.getUserId(), product.getName(), strategy.getPlatformId(), product.getSymbolName());
            // 过滤掉空的单向持仓
            List<JSONObject> positionList = new ArrayList<>();
            if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                positionList = positions.toJavaList(JSONObject.class).stream()
                        .filter(i -> !"net".equals(i.getString("posSide")))
                        .collect(Collectors.toList());
            }

            for (JSONObject position : positionList) {
                // 持仓量（张）
                Integer nowNum = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    nowNum = position.getInteger("pos");
                }
                nowNum = Objects.isNull(nowNum) ? 0 : nowNum;
                strategy.setSheetNumNow(strategy.getSheetNumNow() + nowNum);

                // 持仓价值（USDT）
                BigDecimal nowValue = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    nowValue = position.getBigDecimal("notionalUsd");
                }
                nowValue = Objects.isNull(nowValue) ? BigDecimal.ZERO : nowValue.setScale(2, RoundingMode.HALF_UP);
                strategy.setValueNow(strategy.getValueNow().add(nowValue));

                // 持仓均价
                BigDecimal avgOpen = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    avgOpen = position.getBigDecimal("avgPx");
                }
                avgOpen = Objects.isNull(avgOpen) ? BigDecimal.ZERO : avgOpen.setScale(2, RoundingMode.HALF_UP);
                strategy.setAvgOpen(strategy.getAvgOpen().add(avgOpen));

                // 盈亏
                BigDecimal profit = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    profit = position.getBigDecimal("upl");
                }
                profit = Objects.isNull(profit) ? BigDecimal.ZERO : profit.setScale(2, RoundingMode.HALF_UP);
                strategy.setProfit(strategy.getProfit().add(profit));

                // 初始保证金
                BigDecimal margin = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    margin = position.getBigDecimal("imr");
                }
                margin = Objects.isNull(margin) ? BigDecimal.ZERO : margin.setScale(2, RoundingMode.HALF_UP);
                strategy.setMargin(strategy.getMargin().add(margin));
            }

            // 持仓均价除于2
            if (positionList.size() == 2) {
                strategy.setAvgOpen(strategy.getAvgOpen().divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP));
            }

            // 收益率
            BigDecimal profitRate = StrategyOrderContractCalculator.profitRate(strategy.getProfit(), strategy.getMargin());
            strategy.setProfitRate(profitRate.setScale(2, RoundingMode.HALF_UP));
            // 持仓量（币）
            strategy.setNumNow(product.getSheetValue().multiply(BigDecimal.valueOf(strategy.getSheetNumNow())));
            // 查询价格
            BigDecimal lastPrice = null;
            if (product.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                lastPrice = redisUtils.getSymbolPrice(strategy.getSymbolName(), CommonRedisConstants.OKX + CommonRedisConstants.LAST_PRICE);
            }
            strategy.setPrice(lastPrice);

        } catch (ApiRequestException ignore) {
        }

        return list;

    }

    /**
     * 查询用户当前策略
     *
     * @param userId 用户id
     * @return {@link StrategyContractAppDetailVO}
     * @since 2022/12/9 19:11
     */
    @Override
    public StrategyContractAppDetailVO appCurrent(Long userId) {

        // 查询开关
        QueryWrapper<StrategySwitchContract> querySwitch = new QueryWrapper<>();
        querySwitch.lambda()
                .eq(StrategySwitchContract::getUserId, userId)
                .in(StrategySwitchContract::getStatus,
                        StrategySwitchContractEnum.Status.PAUSE.getValue(),
                        StrategySwitchContractEnum.Status.RUNNING.getValue(),
                        StrategySwitchContractEnum.Status.ERROR.getValue());
        StrategySwitchContract switchContract = strategySwitchContractMapper.selectOne(querySwitch);
        if (Objects.isNull(switchContract)) {
            return null;
        }

        // 查询策略
        StrategyContract strategy = strategyContractUtil.getStrategyCache(switchContract.getStrategyId(), true);
        if (Objects.isNull(strategy)) {
            throw new DuckweedException("策略状态信息已改变");
        }
        StrategyContractAppDetailVO vo = StrategyContractStruct.INSTANCE.entity2appDetailVO(strategy);
        vo.setSide(StrategyContractEnum.Side.findName(vo.getSide()));
        vo.setSwitchId(switchContract.getId());
        vo.setSwitchStatus(StrategySwitchContractEnum.Status.findName(switchContract.getStatus()));
        vo.setSwitchCreateTime(switchContract.getCreateTime());

        // 查询币对
        SystemSymbol symbol = redisUtils.getPersistentCache(RedisKeyEnum.SYMBOL, strategy.getSymbolName(), SystemSymbol.class);
        if (!Objects.isNull(symbol)) {
            vo.setImage(symbol.getImage());
        }

        // 查询策略组
        StrategyGroupContract group = strategyContractUtil.getStrategyGroupCache(strategy.getGroupId(), false);
        vo.setGroupName(group.getName());
        vo.setFuelFrozen(group.getFuelFrozen());

        // 查询总利润
        QueryWrapper<StrategySwitchContract> queryProfitTotal = new QueryWrapper<>();
        queryProfitTotal.select("ifNull(sum(profit), 0) profit");
        querySwitch.lambda()
                .eq(StrategySwitchContract::getStrategyId, vo.getId())
                .eq(StrategySwitchContract::getUserId, userId);
        StrategySwitchContract profitTotal = strategySwitchContractMapper.selectOne(querySwitch);
        if (Objects.isNull(profitTotal)) {
            throw new DuckweedException("策略状态信息已改变");
        }
        vo.setProfitTotal(profitTotal.getProfit());

        // 查询产品
        SystemProduct product = strategyContractUtil.getProductById(strategy.getProductId(), false);
        // 查询价格
        BigDecimal lastPrice = null;
        if (product.getPlatformId().equals(SystemPlatformConstant.OKX)) {
            lastPrice = redisUtils.getSymbolPrice(strategy.getSymbolName(), CommonRedisConstants.OKX + CommonRedisConstants.LAST_PRICE);
        }
        vo.setPrice(lastPrice);

        // 查询任务id
        QueryWrapper<StrategyTaskContract> queryTask = new QueryWrapper<>();
        queryTask.lambda()
                .eq(StrategyTaskContract::getSwitchId, vo.getSwitchId())
                .eq(StrategyTaskContract::getUserId, userId)
                .in(StrategyTaskContract::getStatus,
                        StrategyTaskContractEnum.Status.PAUSE.getValue(),
                        StrategyTaskContractEnum.Status.RUNNING.getValue(),
                        StrategyTaskContractEnum.Status.ERROR.getValue());
        StrategyTaskContract task = strategyTaskContractMapper.selectOne(queryTask);
        if (Objects.isNull(task)) {
            throw new DuckweedException("策略状态信息已改变");
        }

        // 下单次数已满 || 任务不是异常状态
        if (task.getOrderCount().equals(strategy.getOrderNum()) ||
                !Objects.equals(StrategyTaskContractEnum.Status.ERROR.getValue(), task.getStatus())) {
            // 根据任务id查询任务缓存
            StrategyTaskContractCacheBO taskCache = strategyContractUtil.getStrategyTaskCache(task.getId(), false);

            // 价格波动比例（%）
            BigDecimal fluctuationRatio = StrategyTaskContractCalculator.fluctuationRatio(strategy.getFluctuationRatio(),
                    strategy.getFluctuationIncrease(),
                    task.getAddCount());

            // 计算下次补仓价格
            BigDecimal nextAddPrice = StrategyTaskContractCalculator.nextAddPrice(task.getFirstAddSide(),
                    vo.getPrice(),
                    taskCache.getLastOpenAvg(),
                    fluctuationRatio);
            // 预估补仓价格
            vo.setNextAddPrice(nextAddPrice.setScale(2, RoundingMode.HALF_UP));
            // 下次补仓比例
            vo.setNextAddRatio(fluctuationRatio.setScale(2, RoundingMode.HALF_UP));
        }

        try {
            // 查询仓位信息
            JSONArray positions = strategyOrderContractHandlerManager.positions(userId, product.getName(), strategy.getPlatformId(), product.getSymbolName());
            // 过滤掉空的单向持仓
            List<JSONObject> positionList = new ArrayList<>();
            if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                positionList = positions.toJavaList(JSONObject.class).stream()
                        .filter(i -> !"net".equals(i.getString("posSide")))
                        .collect(Collectors.toList());
            }

            for (JSONObject position : positionList) {
                // 预估强平价（锁仓时两个仓位强平价一致）
                BigDecimal liquidatePrice = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    liquidatePrice = position.getBigDecimal("liqPx");
                }
                vo.setLiquidatePrice(Objects.isNull(liquidatePrice) ? null : liquidatePrice.setScale(2, RoundingMode.HALF_UP));

                // 盈亏
                BigDecimal profit = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    profit = position.getBigDecimal("upl");
                }
                profit = Objects.isNull(profit) ? BigDecimal.ZERO : profit.setScale(2, RoundingMode.HALF_UP);
                vo.setProfit(vo.getProfit().add(profit));

                // 初始保证金
                BigDecimal margin = null;
                if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                    margin = position.getBigDecimal("imr");
                }
                margin = Objects.isNull(margin) ? BigDecimal.ZERO : margin.setScale(2, RoundingMode.HALF_UP);
                vo.setMargin(vo.getMargin().add(margin));
            }

            // 收益率
            BigDecimal profitRate = StrategyOrderContractCalculator.profitRate(vo.getProfit(), vo.getMargin());
            vo.setProfitRate(profitRate.setScale(2, RoundingMode.HALF_UP));

        } catch (ApiRequestException ignore) {
        }

        return vo;

    }

    /**
     * 查询用户利润
     *
     * @return {@link StrategyContractProfitVO}
     * @since 2022/11/24 11:30
     */
    @Override
    public StrategyContractProfitVO profit() {
        // 获取登录用户
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        return profit(user.getId());
    }

    /**
     * 查询用户利润
     *
     * @return {@link StrategyContractProfitVO}
     * @since 2022/11/24 11:30
     */
    @Override
    public StrategyContractProfitVO profit(Long userId) {
        // 获取昨天的时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DAY_OF_MONTH, -1);
        Date yesterday = calendar.getTime();

        // 累计利润
        BigDecimal profitTotal = strategyTaskContractMapper.profit(userId, null, null);
        // 昨日利润
        BigDecimal profitYesterday = strategyTaskContractMapper.profit(userId, yesterday, yesterday);

        StrategyContractProfitVO vo = new StrategyContractProfitVO();
        vo.setProfitTotal(profitTotal.setScale(2, RoundingMode.HALF_UP));
        vo.setProfitYesterday(profitYesterday.setScale(2, RoundingMode.HALF_UP));

        return vo;

    }

}
